﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace PddHelper.Framework
{
    /// <summary>
    /// lambad扩展帮助类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LambdaExtension<T>
    {
        /// <summary>
        /// 
        /// </summary>
        public Expression<Func<T, bool>> expression;

        /// <summary>
        /// 
        /// </summary>
        public LambdaExtension()
        {
            expression = p => true;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>true
        /// </summary>
        /// <returns></returns>
        public LambdaExtension<T> True()
        {
            expression.And(ex => true);
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>false
        /// </summary>
        /// <returns></returns>
        public LambdaExtension<T> False()
        {
            expression.And(ex => false);
            return this;
        }

        /// <summary>
        /// 获取排序表达式
        /// orderby使用
        /// </summary> 
        /// <param name="propertyPath"></param>
        /// <returns></returns>
        public static Expression<Func<T, object>> GetOrderBy(string propertyPath)
        {
            var param = Expression.Parameter(typeof(T), "p");
            var parts = propertyPath.Split('.');
            Expression parent = param;
            foreach (var part in parts)
            {
                parent = Expression.Property(parent, part);
            }

            if (parent.Type.IsValueType)
            {
                var converted = Expression.Convert(parent, typeof(object));
                return Expression.Lambda<Func<T, object>>(converted, param);
            }
            else
            {
                return Expression.Lambda<Func<T, object>>(parent, param);
            }
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name
        /// 主要是排序使用
        /// </summary>
        /// <typeparam name="TKey">参数类型</typeparam>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <returns></returns>
        public static Expression<Func<T, TKey>> GetOrder<TKey>(string name)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), "ex");
            return Expression.Lambda<Func<T, TKey>>(Expression.Property(parameter, name), parameter);
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name == value
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> Equal(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Equal(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name != value
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> NotEqual(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.NotEqual(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name > value
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> GreaterThan(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name >= value
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> GreaterThanOrEqual(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：
        /// ex=>ex.name <= value 
        /// </summary>
        /// <typeparam name="T">对象名称（类名）</typeparam>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> LessThan(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.LessThan(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：p=> p.name 小于= value 
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> LessThanOrEqual(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");//创建参数p
            var member = Expression.PropertyOrField(parameter, name);
            var constant = ChangeConstant(name, value);//创建常数
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.LessThanOrEqual(member, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name.StartsWith(value)
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LambdaExtension<T> StartsWith(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");
            var member = Expression.PropertyOrField(parameter, name);
            var method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
            var constant = ChangeConstant(name, value);
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Call(member, method, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name.EndsWith(value)
        /// </summary> 
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public LambdaExtension<T> EndsWith(string name, object value)
        {
            var parameter = Expression.Parameter(typeof(T), "ex");
            var member = Expression.PropertyOrField(parameter, name);
            var method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
            var constant = ChangeConstant(name, value);
            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Call(member, method, constant), parameter));
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name.Contains(value)
        /// </summary>
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> Contains(string name, object value)
        {
            if (value.GetType().GetGenericTypeDefinition() == typeof(List<>))
            {
                ContainsArr(name, value);
            }
            else
            {
                var parameter = Expression.Parameter(typeof(T), "ex");
                var member = Expression.PropertyOrField(parameter, name);
                var valueType = typeof(T).GetProperties().Where(ex => ex.Name.ToLower() == name.ToLower())?.FirstOrDefault()?.PropertyType;
                var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });

                ConstantExpression constant = ChangeConstant(name, value);
                expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Call(member, method, constant), parameter));
            }
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>ex.name.Contains(value)
        /// </summary>
        /// <param name="name"></param>
        /// <param name="array"></param>
        private void ContainsArr(string name, object array)
        {
            var valueType = typeof(T).GetProperties().Where(ex => ex.Name.ToLower() == name.ToLower())?.FirstOrDefault()?.PropertyType;

            var parameter = Expression.Parameter(typeof(T), "ex");
            var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Single(x => x.Name == "Contains" && x.GetParameters().Length == 2).MakeGenericMethod(valueType);

            var property = Expression.PropertyOrField(parameter, name);
            var body = Expression.Call(contains, Expression.Constant(array), property);

            expression = expression.And(Expression.Lambda<Func<T, bool>>(body, parameter));
        }

        /// <summary>
        /// 创建lambda表达式：!(ex=>ex.name.Contains(value))
        /// </summary> 
        /// <param name="name">字段名称（数据库中字段名称）</param>
        /// <param name="value">数据值</param>
        /// <returns></returns>
        public LambdaExtension<T> NotContains(string name, object value)
        {
            if (value.GetType().GetGenericTypeDefinition() == typeof(List<>))
            {
                NotContainsArr(name, value);
            }
            else
            {
                var parameter = Expression.Parameter(typeof(T), "ex");
                var member = Expression.PropertyOrField(parameter, name);
                var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                var constant = ChangeConstant(name, value);
                expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Not(Expression.Call(member, method, constant)), parameter));
            }
            return this;
        }

        /// <summary>
        /// 创建lambda表达式：ex=>!(ex.name.Contains(value))
        /// </summary>
        /// <param name="name"></param>
        /// <param name="array"></param>
        private void NotContainsArr(string name, object array)
        {
            var valueType = typeof(T).GetProperties().Where(ex => ex.Name.ToLower() == name.ToLower())?.FirstOrDefault()?.PropertyType;

            var parameter = Expression.Parameter(typeof(T), "ex");
            var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Single(x => x.Name == "Contains" && x.GetParameters().Length == 2).MakeGenericMethod(valueType);

            var property = Expression.PropertyOrField(parameter, name);
            var body = Expression.Call(contains, Expression.Constant(array), property);

            expression = expression.And(Expression.Lambda<Func<T, bool>>(Expression.Not(body), parameter));
        }

        /// <summary>
        /// 根据实体字段类型,强制转换为lambda对应的常量类型
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private static ConstantExpression ChangeConstant(string name, object value)
        {
            object obj = null;
            var valueType = typeof(T).GetProperties().Where(ex => ex.Name.ToLower() == name.ToLower())?.FirstOrDefault()?.PropertyType;
            var _type = valueType;
            if (_type != null && value != null)
            {
                if (_type.IsGenericType && _type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                {
                    _type = Nullable.GetUnderlyingType(_type);
                }
                obj = Convert.ChangeType(value, _type);
            }
            return Expression.Constant(obj, valueType);
        }
    }
}
